La gestion des chemins de fichiers avec Python, c'est toute une histoire !

Python est excellent pour crer des scripts qui ont pour vocation de grer des fichiers et des dossiers. J'ai personnellement une bonne dizaine de scripts qui tournent tous les jours sur ma machine pour diffrentes tches de classification et organisation de mon systme d'exploitation.

Historiquement, on utilisait des modules comme le module os, glob ou encore shutil pour excuter des oprations de cration, suppression et gestion des fichiers.

Le module pathlib, disponible depuis la version 3.4 de Python, permet d'effectuer presque toutes les oprations courantes sur un systme d'exploitation, avec une syntaxe oriente objet beaucoup plus agrable utiliser.

La classe Path

La classe Path permet de crer un objet reprsentant un chemin vers un fichier ou un dossier de notre ordinateur. Ce chemin peut exister ou ne pas exister sur notre disque dur, ce n'est pas un prrequis.

La classe Path dispose de plusieurs mthodes de classes permettant d'accder des chemins courants de notre systme d'exploitation, comme le dossier utilisateur :

from pathlib import Path

dossier_utilisateur = Path.home()

On peut galement rcuprer le dossier courant :

from pathlib import Path

dossier_courant = Path.cwd()

Ou encore crer un chemin spcifique en passant une chane de caractres la classe Path :

from pathlib import Path

documents = Path("/Users/thibh/Documents")

Si on affiche l'objet cr partir de la classe Path, on se retrouve avec un objet PosixPath. Cet objet reprsente les chemins des systmes Linux et Mac OS. Sur Windows, l'objet sera diffrent, car les chemins sur Windows ne sont pas les mmes que sur Mac et Linux. Cela ne change cependant rien aux mthodes et attributs que l'on peut utiliser sur cet objet.

>>> from pathlib import Path
>>> documents = Path("/Users/thibh/Documents")
>>> print(documents)
PosixPath('/Users/thibh/Documents')

Concatner des chemins

Pour concatner des chemins, c'est trs simple, il suffit d'utiliser une barre oblique (un slash en bon franglais) :

from pathlib import Path

home = Path.home()              # PosixPath('/Users/thibh/')
documents = home / "Documents"  # PosixPath('/Users/thibh/Documents')

Le rsultat de cette concatnation nous retourne un nouvel objet PosixPath, nous pouvons donc concatner plusieurs chanes de caractres la suite.

pathlib se charge de son ct de grer les diffrents systmes d'exploitation et d'utiliser un slash ou un antislash selon que vous utilisiez Mac / Linux ou Windows. Ce comportement est donc similaire la fonction os.path.join du module os.

from pathlib import Path

home = Path.home()                         # PosixPath('/Users/thibh/')
documents = home / "Documents" / "Projet"  # PosixPath('/Users/thibh/Documents/Projet')

On peut galement utiliser la mthode joinpath sur un objet Path. a peut tre pratique si vous avez par exemple une liste de dossiers que vous souhaitez concatner (grce l'unpacking et l'oprateur splat *) :

from pathlib import Path

home = Path.home()                        # PosixPath('/Users/thibh/')
dossiers = ['Projets', 'Django', 'blog']
home.joinpath(*dossiers)                  # PosixPath('/Users/thibh/Projets/Django/blog')

Si vous utilisez des slashs et que vous souhaitez utiliser une mthode de l'objet Path, pensez utiliser les parenthses pour entourer les chemins concatns :

from pathlib import Path

home = Path.home() # PosixPath('/Users/thibh/')

# Ne fonctionne pas car on essaie de rcuprer l'attribut suffix sur la chane de caractres "main.py"
home / "Projet" / "main.py".suffix

# Avec des parenthses, a fonctionne !
(home / "Projet" / "main.py").suffix

Rcuprer des informations sur un chemin

Grce l'orient objet, on peut accder de nombreuses informations sur un chemin avec les attributs de l'objet Path :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.name    # "index.html"
p.parent  # "/Users/thibh/Documents"
p.stem    # "index"
p.suffix  # ".html"
p.parts   # ("/", "Users", "thibh", "documents", "index.html")

Il existe galement des mthodes qui permettent de vrifier l'existence et le type d'un chemin :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.exists()   # True
p.is_dir()   # False
p.is_file()  # True

L encore, quand un chemin peut nous tre retourn par un de ces attributs, on rcupre un objet Path.

On peut donc mettre bout bout plusieurs fois le mme attribut pour remonter de plusieurs dossiers par exemple :

from pathlib import Path

p = Path("/Users/thibh/Documents/index.html")
p.parent.parent  # "/Users/thibh"

Crer et supprimer des dossiers

Pour crer un dossier, on peut utiliser la mthode mkdir. Cette mthode ne fonctionnera par dfaut que si le dossier n'existe pas.

Vous pouvez utiliser le paramtre exist_ok pour signifier que vous ne souhaitez pas qu'une erreur soit leve si le dossier existe dj :

from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
dossier.mkdir()  # Lve une erreur si le dossier existe dj
dossier.mkdir(exist_ok=True)

Si vous souhaitez crer une hirarchie de dossier qui n'existent pas, il faut ajouter le paramtre parents :

from pathlib import Path

# Le dossier SiteWeb et ses sous-dossiers n'existent pas
dossier = Path("/Users/thibh/Documents/SiteWeb/sources/css")

# On peut tout crer d'un coup avec le paramtre parents
dossier.mkdir(parents=True)

Pour supprimer un dossier, on utilise la mthode rmdir :

from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
dossier.rmdir()

Cette mthode ne fonctionne que si le dossier est vide.

Si le dossier contient des fichiers ou d'autres sous-dossiers, cette mthode ne fonctionne pas, et c'est le seul cas de figure o nous sommes obligs de repasser par le module shutil et la fonction rmtree :

import shutil
from pathlib import Path

dossier = Path("/Users/thibh/Documents/SiteWeb")
shutil.rmtree(dossier)

Crer, lire et crire dans un fichier

Pour crer et supprimer un fichier, on peut utiliser respectivement les mthodes touch et unlink :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
fichier.touch()   # On cre le fichier
fichier.unlink()  # On supprime le fichier

Pour crire du contenu dans un fichier, on utilise la mthode write_text :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
fichier.write_text("Accueil du site")

Il n'est pas obligatoire de crer le fichier au pralable avec la mthode touch.

Vous conviendrez que c'est plus rapide que de faire :

from pathlib import Path

fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
with open(fichier, "w") as f:
    f.write("Accueil du site")

De la mme faon, pour lire le contenu d'un fichier, on peut utiliser la mthode read_text :

>>> from pathlib import Path

>>> fichier = Path("/Users/thibh/Documents/SiteWeb/index.html")
>>> fichier.read_text()
"Accueil du site"

Scanner un dossier

L o pathlib rayonne, c'est vraiment dans la possibilit de scanner les dossiers de votre ordinateur avec des mthodes beaucoup plus faciles retenir que le module glob.

Pour rcuprer tous les fichiers et dossiers l'intrieur d'un dossier, on peut utiliser la mthode iterdir :

from pathlib import Path

for f in Path.home().iterdir():
    print(f.name)

On peut combiner cette mthode avec la mthode is_dir pour ne rcuprer que les dossiers (ici avec une comprhension de liste) :

from pathlib import Path

dossiers = [d for d in Path.home().iterdir() if d.is_dir()]

Pour scanner un dossier avec un peu plus de flexibilit, on peut utiliser la mthode glob (et oui, comme le module !). On peut par exemple ne rcuprer que les fichiers dont l'extension est .png :

from pathlib import Path

for f in Path.home().glob("*.png"):
    print(f.name)

Si vous souhaitez scanner un dossier de faon rcursive, il suffit d'utiliser rglob la place de glob :

from pathlib import Path

for f in Path.home().rglob("*.png"):
    print(f.name)

Facile non ?

Quelques cas pratiques

Voici quelques cas pratiques qui vous montrent la flexibilit et la facilit d'utilisation de pathlib.

Ajouter un suffixe un nom de fichier

from pathlib import Path

p = Path.home() / "image.png"               # "/Users/thibh/image.png"
p.parent / (p.stem + "-lowres" + p.suffix)  # "/Users/thibh/image-lowres.png"

Trier des fichiers selon leur extension

from pathlib import Path

dirs = {".jpg": "Images",
        ".gif": "Images",
        ".mp4": "Videos",
        ".pdf": "Documents",
        ".mp3": "Musiques",
        ".wav": "Musiques"}

tri_dir = Path.home() / "Tri"
files = [f for f in tri_dir.iterdir() if f.is_file()]
for f in files:
    # Si aucune correspondance n'est trouv pour l'extension, on place les fichiers dans un dossier Autres
    output_dir = tri_dir / dirs.get(f.suffix, "Autres")
    output_dir.mkdir(exist_ok=True)
    f.rename(output_dir / f.name)

On peut utiliser la mthode rename pour dplacer un fichier.

Crer les constantes d'un dossier avec file

from pathlib import Path

SOURCE_FILE = Path(__file__).resolve()  # resolve permet de rsoudre les liens symboliques
SOURCE_DIR = SOURCE_FILE.parent
ROOT_DIR = SOURCE_DIR.parent
DATA_DIR = SOURCE_DIR / "DATA"